#include <hidef.h> /* for EnableInterrupts macro */
#include "derivative.h" /* include peripheral declarations */
#include <string.h>
#include <stdio.h>
#include "I2C.h"

//#############################################################
//                                                            #
//                          Defines                           #                
//                                                            #
//#############################################################
#define PLUG1 PTA_PTA0
#define PLUG1_ON{PTA_PTA0 = 1;}
#define PLUG1_OFF{PTA_PTA0 = 0;}
#define PLUG2 PTA_PTA1
#define PLUG2_ON{PTA_PTA1 = 1;}
#define PLUG2_OFF{PTA_PTA1 = 0;}
#define PLUG3 PTA_PTA4
#define PLUG3_ON{PTA_PTA4 = 1;}
#define PLUG3_OFF{PTA_PTA4 = 0;}
#define PLUG4 PTA_PTA5
#define PLUG4_ON{PTA_PTA5 = 1;}
#define PLUG4_OFF{PTA_PTA5 = 0;}
#define RXSIZE 128
#define ERARNGE 0xFCBE      /* FLASH erase routine in internal ROM */
#define PRGRNGE 0xFC06  /* FLASH program routine in internal ROM */
#define LDRNGE  0xFF30  /* Load FLASH data into RAM data array */

//#############################################################
//                                                            #
//                 Global Constant Variables                  #                
//                                                            #
//#############################################################
#pragma CONST_SEG DEFAULT
const unsigned char WRITE=0;
const unsigned char READ=1; 
const unsigned char RTC_ADDR=0b11010000;
const unsigned char EEPROM_ADDR=0b10100000;
const int PLUG1_ADDR=0xEF00;
const int PLUG2_ADDR=0xEF80;
const int PLUG3_ADDR=0xF000;
const int PLUG4_ADDR=0xF080;
const int PLUG_ADDR[4] = {PLUG1_ADDR,PLUG2_ADDR,PLUG3_ADDR,PLUG4_ADDR};
typedef struct {
  char onHour;
  char onMin;  
  char offHour;
  char offMin;      
} TimeSlotPair;


//#############################################################
//                                                            #
//                      Global Variables                      #                
//                                                            #
//#############################################################
#pragma DATA_SEG __SHORT_SEG MY_ZEROPAGE //256 bytes
unsigned char BUS_SPD @0x0060; /* Value should equal 4x Bus speed */
unsigned char DATASIZE @0x0061; /* Number of bytes in data array (max 255) */
unsigned int  ADDR @0x0062; /* Start address of FLASH to manipulate */
unsigned char flashBuffer[128] @0x0064;
unsigned int *FILE_PTR = (unsigned int *)(&BUS_SPD); /* Address of RAM DATA Block */ 
unsigned char delay0;
unsigned char delay1;
unsigned char timerCount,updateClock,updatePlug;
#pragma DATA_SEG DEFAULT
unsigned char currentSecond,currentMinute,currentHour,currentDay;
unsigned char currentSecondBCD,currentMinuteBCD,currentHourBCD,currentDayBCD;
TimeSlotPair timeSlot[4];

//#############################################################
//                                                            #
//                   Queue Functions                          #
//                                                            #
//#############################################################
#pragma DATA_SEG __SHORT_SEG MY_ZEROPAGE
unsigned char rxReadIndex,rxWriteIndex,rxCharCount;
#pragma DATA_SEG DEFAULT
unsigned char rxBuffer[RXSIZE];


#pragma CODE_SEG DEFAULT
/*******************************
 * queue_init - initialize queue
 *******************************/
void queue_init(void){
  rxReadIndex=rxWriteIndex=rxCharCount=0;      
}

/********************************************************
 * queue_size - returns number of char in queue
 * Main routine checks if char is in queue before reading
 ********************************************************/
unsigned char rxBuffer_size(void){
 return rxCharCount;   
}

//#############################################################
//                                                            #
//                     SCI Functions                          #
//                                                            #
//#############################################################

/************************************
 * SCI_init - Initialize the SCI port
 ************************************/
void SCI_init(void) {    
    SCC1 = 0b01000100;       //Enable SCI,8N1,    
    //9.6k (1,1)
    SCBR_SCP = 1;            // Baud rate prescaler = 0
    SCBR_SCR = 1;            // Baud rate divisor = 0    
    SCC2_RE = 1;             // Enable receiver
    SCC2_SCRIE = 1;          // Enable recieve interrupt
    SCC2_TE = 1;             // Enable transmitter        
    
}

/************************************
 * sciread - Read a byte from SCI port
 ************************************/
unsigned char sciread() 
{
  unsigned char byte;
  if(rxCharCount>0){
    byte = rxBuffer[rxReadIndex++];
    rxReadIndex &= RXSIZE-1;
    rxCharCount--;   
    return byte;    
  } else
  return -1;
}

/***********************************
 * sciout - Output a byte to SCI port
 ***********************************/

void sciout(unsigned char ch)
{
    while (SCS1_SCTE == 0);
    SCDR = ch;
}

/************************************************************
 * sciouts - Print the contents of the character string "s" 
 * to the SCI port. String must be terminated by a zero byte.
 ************************************************************/
void sciouts(unsigned char *s)
{
    unsigned char ch;
    while ((ch = *s++) != (unsigned char) NULL)
        sciout(ch);
}

/****************************
 * SCI Recieve Interrupt
 * Add recieved data to queue
 ****************************/
interrupt 13 void scirISR(void){           
    SCS1 &= ~(0x20);                
    rxBuffer[rxWriteIndex++]=SCDR;
    rxWriteIndex &= RXSIZE-1;
    rxCharCount++;    
}

//#############################################################
//                                                            #
//                     RTC Functions                          #
//                                                            # 
//#############################################################

/****************************************************
 * rtc_init - initializes the RTC Tuesday 4/4/09
 ****************************************************/
void rtc_init(void){
  I2C_start();
  I2C_tx_byte(RTC_ADDR|WRITE); //RTC Address
  I2C_tx_byte(0); //Memory address 0
  I2C_tx_byte(0); //Start Oscillator, 0 seconds
  I2C_tx_byte(0);	//0 minutes
  I2C_tx_byte(0); //24 hour mode, 0 hours
  I2C_tx_byte(5); //Day 5, Thursday
  I2C_tx_byte(2); //Date 2nd
  I2C_tx_byte(4); //April
  I2C_tx_byte(9); //2009
  I2C_tx_byte(0); //Control register to 0
  I2C_stop();     
}

/****************************************************
 * RTC_Update - gets the Time from the RTC
 * Gets second, minute, hour, day, date, month, year
 ****************************************************/
void RTC_Update(){
  I2C_start();
  I2C_tx_byte(RTC_ADDR|WRITE);
  I2C_tx_byte(0);
  I2C_start();
  I2C_tx_byte(RTC_ADDR|READ);
  currentSecondBCD=I2C_rx_byte(1);
  currentMinuteBCD=I2C_rx_byte(1);
  currentHourBCD=I2C_rx_byte(1);
  currentDayBCD=I2C_rx_byte(0);
  I2C_stop();    
  currentSecond = ((currentSecondBCD&0x70)>>4)*10+(currentSecondBCD&0x0F);
  currentMinute = ((currentMinuteBCD&0x70)>>4)*10+(currentMinuteBCD&0x0F);
  currentHour = ((currentHourBCD&0x30)>>4)*10+(currentHourBCD&0x0F);
  currentDay = currentDayBCD&0x07;
}

//#############################################################
//                                                            #
//                  Debug Menu Functions                      #
//                                                            #
//#############################################################
/****************************************************
 * printTime - Prints time to terminal
 * Time: 23:59:59 Date: 12/31/99
 ****************************************************/

void printTime(){ 
  sciout('|'); 
  sciout(((currentHourBCD&0x30)>>4)+48);
  sciout((currentHourBCD&0x0F)+48);  
  sciout(':');
  sciout(((currentMinuteBCD&0x70)>>4)+48);
  sciout((currentMinuteBCD&0x0F)+48); 
  sciout(':');
  sciout(((currentSecondBCD&0x70)>>4)+48);
  sciout((currentSecondBCD&0x0F)+48);
  sciout('|');        
  sciout((currentDayBCD&0x07)+48);      
  sciout(13);
  sciout(10);   
}

//#############################################################
//                                                            #
//                    Timer Functions                         #
//                                                            #
//#############################################################

 /****************************************************
 * Timer_init - Setup timer interrupt
 ****************************************************/
void Timer_init(){
    /*
       The board has a 14.7456 MHz clock which makes the processor run
       at 3.6864 MHz.  We want the timer to interrupt every second
       (1 Hz) so we need to count clocks to 3.6864MHz/1Hz = 3,686,400.  This
       is too big for the 16 bit counter register so use the prescaler to
       divide the clock by 64 and then count that clock to 57,600. 1 Second 
    */   
    T1SC_PS = 6;             // set prescalar for divide by 64
    T1SC_TOIE = 1;           // enable timer interrupt
    T1MOD = 57600;           // store modulo value in T1MODH:T1MODL
    T1SC_TSTOP = 0;          // start timer running  
}


/****************************************************
 * timerISR - Every 1 second, check to turn off plugs
 ****************************************************/
interrupt 6 void timerISR(void){       
  //Compare Times
  T1SC_TOF = 0;
  updateClock = 1;
  timerCount++;
  if(timerCount == 60){
    timerCount = 0;
    updatePlug = 1;  
  }
       
}

//#############################################################
//                                                            #
//                    Helper Functions                        #
//                                                            #
//#############################################################

/***************************************************
 * del1m - Delay about 1msec times the "d" argument.
 ***************************************************/
void del1m(int d)
{
    while (d--) {
        asm {
            ; The following code delays 1 millisecond by looping.
            ; Total time is 4 + 7 * (4 + 77 * (4 + 3) + 4 + 3) = 3854 cycles
            ; A 14.7456MHz external clock gives an internal CPU clock of
            ; 3.6864MHz (271.267ns/cycle).  Delay is then 1.04546 milliseconds.
                    mov     #$7,delay1  ; 4 cycles
            m1:     mov     #77,delay0  ; 4 cycles
            m0:     dec     delay0      ; 4 cycles
                    bne     m0          ; 3 cycles
                    dec     delay1      ; 4 cycles
                    bne     m1          ; 3 cycles
        }
    }
}



/****************************************************
 * Plug_init - initializes I/O pins for Plugs
 ****************************************************/
void Plug_init(){
  DDRA_DDRA0 = 1;   /* Set I/O Pin as Output */
  DDRA_DDRA1 = 1;   /* Set I/O Pin as Output */
  DDRA_DDRA4 = 1;   /* Set I/O Pin as Output */
  DDRA_DDRA5 = 1;   /* Set I/O Pin as Output */
  PLUG1_ON;         /* Turn Plug 1 on */
  PLUG2_ON;         /* Turn Plug 2 on */
  PLUG3_ON;         /* Turn Plug 3 on */
  PLUG4_ON;         /* Turn Plug 4 on */  
}

/****************************************************
 * hex2bcd - convert a hex to bcd format
 ****************************************************/
unsigned char hex2bcd (unsigned char x)
{
  unsigned char y;
  y = (x / 10) << 4;
  y = y | (x % 10);
  return (y);
}

/****************************************************
 * timeToValue - convert Time (hour,minute) to int
 ****************************************************/
int timeToValue(unsigned char hour, unsigned char minute){  
  return hour*60+minute;  
}

//#############################################################
//                                                            #
//                     FLASH Functions                        #
//                                                            #
//#############################################################

/****************************************************
 * FLASHErase - Erase a 64 bytes section of FLASH
 ****************************************************/
void FLASHErase(int address) {
  ADDR = address;
  asm(LDHX FILE_PTR);    /* Load address of RAM Data Block to H:X */
  asm(JSR ERARNGE);      /* Call ERARNGE ROM Subroutine */ 
  ADDR = address+0x40;
  asm(LDHX FILE_PTR);    /* Load address of RAM Data Block to H:X */
  asm(JSR ERARNGE);      /* Call ERARNGE ROM Subroutine */ 
  EnableInterrupts;   
}

/****************************************************
 * FLASHProgram - Program a 128 byte section of FLASH
 ****************************************************/
void FLASHProgram(int address) {
  FLASHErase(address);
  ADDR = address;  
  asm(LDHX FILE_PTR);    /* Load address of RAM Data Block to H:X */   
  asm(JSR PRGRNGE);      /* Call PRGRNGE ROM Subroutine */   
  EnableInterrupts;  
}

/****************************************************
 * FLASHLoad - Load a 128 byte section of FLASH
 ****************************************************/  
void FLASHLoad(int address) {
  ADDR = address;
  asm(LDHX FILE_PTR);    /* Load address of RAM Data Block to H:X */
  asm(JSR LDRNGE);    /* Call LDRNGE ROM Subroutine */
  EnableInterrupts;  
}

/****************************************************
 * clearFlashbuffer - Erase flash buffer array
 ****************************************************/
void clearFLASHbuffer(){
  int i;
  for(i=0; i<128;i++){    
    flashBuffer[i]=0xff; 
  }
}


//#############################################################
//                                                            #
//                  Programming Functions                     #
//                                                            #
//#############################################################

/****************************************************
 * getPlug - send plug settings through serial
 * @param plugNum plug number (1-4)
 ****************************************************/
void getPlug(unsigned char plugNum){
  int i;
  clearFLASHbuffer();
  FLASHLoad(PLUG_ADDR[plugNum-1]);
  for(i=0;;){
    if((flashBuffer[i]==0x7F)||(flashBuffer[i]==0xFF)){
      sciout(0x7F);
      break;  
    }
    sciout(flashBuffer[i++]);      
  }  
}

/****************************************************
 * setTime - Sets RTC time
 * Send 3 bytes - Hour,Minute,Day
 ****************************************************/
void setTime(){
  int newHour,newMinute,newDay;
  //Get new time from serial
  while(rxBuffer_size()==0);
  newHour=sciread();
  while(rxBuffer_size()==0);
  newMinute=sciread();
  while(rxBuffer_size()==0);
  newDay=sciread();  
  //Set new time to RTC
  I2C_start();
  I2C_tx_byte(RTC_ADDR|WRITE); //RTC Address
  I2C_tx_byte(0); //Memory address 0
  I2C_tx_byte(0); //Start Oscillator, 0 seconds
  I2C_tx_byte(hex2bcd(newMinute));	//New Minute
  I2C_tx_byte((hex2bcd(newHour))&0x3F); //New Hour
  I2C_tx_byte(newDay); //1-7,Sun-Sat
  I2C_tx_byte(2); //Date 2nd
  I2C_tx_byte(4); //April
  I2C_tx_byte(9); //2009
  I2C_tx_byte(0); //Control register to 0
  I2C_stop();      
  //Update current time
  RTC_Update();    
  sciout(0xFC);   
}

/****************************************************
 * checkPlugs - Compare current time with programmed
 *              time to turn on or off plugs
 ****************************************************/
void checkPlugs(){
  unsigned char i,j,k,l;
  unsigned char p[4]={0,0,0,0};
  int onValue,offValue,currentValue;  
  currentValue = timeToValue(currentHour,currentMinute);    
  
  for(i=0;i<4;i++){
    k=0;    
    //Get Settings from FLASH
    clearFLASHbuffer();    
    FLASHLoad(PLUG_ADDR[i]);
     
    //Store current timeSlots    
    for(j=0;;j+=4){
      //Check for termination byte
      if((flashBuffer[j]==0x7F)||(flashBuffer[j]==0xFF)){
        break;
      }
      //Check to see if it is the same day
      if(((flashBuffer[j]&0xE0)>>5)==currentDay){        
        //If it is the same day, store in timeSlot
        timeSlot[k].onHour  = flashBuffer[j]&0x1F;
        timeSlot[k].onMin   = flashBuffer[j+1]&0x3F;
        timeSlot[k].offHour = flashBuffer[j+2]&0x1F;
        timeSlot[k].offMin  = flashBuffer[j+3]&0x3F;
        k++;    
      }               
    }//End store current timeSlots
    
    //Check timeSlots with current Time
    //l is timeSlots counter,k is number of timeSlots
    for(l=0;l<k;l++){
      onValue  = timeToValue(timeSlot[l].onHour,timeSlot[l].onMin);
      offValue = timeToValue(timeSlot[l].offHour,timeSlot[l].offMin);
      //If Current Time is in between an On and Off time, turn it on
      if((currentValue>=onValue)&&(currentValue<offValue+1)){
        p[i]=1;             
      }      
    }//End check timeSlots w/ current Time
    
  }//End for loop 
   
  //Set plugs
  PLUG1 = p[0];
  PLUG2 = p[1];
  PLUG3 = p[2];
  PLUG4 = p[3];

}

//#############################################################
//                                                            #
//                      Main Routine                          #
//                                                            #
//#############################################################
void main(void) {

  //Variables  
  unsigned char in,minute,second,hour,day,date,month,year,i,temp;  

  //Initialize
  CONFIG1_COPD = 1; /* disable COP reset */
  EnableInterrupts; /* enable interrupts */  
  queue_init();     /* initialize queue */
  SCI_init();       /* initialize SCI */  
  I2C_init();       /* initialize I2C */ 
  Plug_init();      /* initialize Plug I/O pins */ 
  Timer_init();     /* initialize Timer interrupt */
  DATASIZE = 128;   /* flash data block of 128 bytes */
  BUS_SPD = 14;     /* 3.6864*4 */ 
  timerCount = 0;   /* initialize timerCount */
  updateClock = 1;  /* Get RTC time */
  updatePlug = 1;   /* Update Plug */  
  
  //Main loop
  for(;;) {
    //Update Clock every second-ish  
    if(updateClock){
      updateClock = 0;
      RTC_Update();  
    }
    
    //Update Plugs
    if(updatePlug){
      updatePlug = 0;
      checkPlugs();  
    }    
    
    //Check for char in buffer     
    if(rxBuffer_size()!=0){       
      //Read buffer  
      in=sciread();
      //Do Action         
      switch(in){
        case '1': //Print Time                                
                printTime();               
                break;
        case '2': //Reset RTC
                rtc_init();                  
                break;
        case 't': //Repeated Print Time
                for(;;){
                  if(sciread()=='s')
                    break;
                  RTC_Update();
                  printTime();
                  del1m(1000);
                }
                break;
        case 'a':
              if(PLUG1){    
                PLUG1_OFF;
              } else {
                PLUG1_ON; 
              }
              break;
        case 'b':
              if(PLUG2){    
                PLUG2_OFF;
              } else {
                PLUG2_ON; 
              }
              break;
        case 'c':
              if(PLUG3){    
                PLUG3_OFF;
              } else {
                PLUG3_ON; 
              }
              break;
        case 'd':
              if(PLUG4){    
                PLUG4_OFF;
              } else {
                PLUG4_ON; 
              }     
              break; 
        case 'h':
              sciout(10);
              sciout(13);
              clearFLASHbuffer();              
              flashBuffer[0] = 'a';
              FLASHProgram(PLUG_ADDR[0]);
              sciout(flashBuffer[0]);              
              clearFLASHbuffer();
              sciout(flashBuffer[0]);
              FLASHLoad(PLUG_ADDR[0]);
              sciout(flashBuffer[0]);                           
              break;                  
        case 'e':
              sciout(10);
              sciout(13);
              clearFLASHbuffer();              
              flashBuffer[0] = 'b';
              FLASHProgram(PLUG_ADDR[1]);
              sciout(flashBuffer[0]);              
              clearFLASHbuffer();
              sciout(flashBuffer[0]);
              FLASHLoad(PLUG_ADDR[1]);
              sciout(flashBuffer[0]);                           
              break; 
        case 'f':
              sciout(10);
              sciout(13);
              clearFLASHbuffer();              
              flashBuffer[0] = 'c';
              FLASHProgram(PLUG_ADDR[2]);
              sciout(flashBuffer[0]);              
              clearFLASHbuffer();
              sciout(flashBuffer[0]);
              FLASHLoad(PLUG_ADDR[2]);
              sciout(flashBuffer[0]);                          
              break; 
        case 'g':
              sciout(10);
              sciout(13);
              clearFLASHbuffer();              
              flashBuffer[0] = 'd';
              FLASHProgram(PLUG_ADDR[3]);
              sciout(flashBuffer[0]);              
              clearFLASHbuffer();
              sciout(flashBuffer[0]);
              FLASHLoad(PLUG_ADDR[3]);
              sciout(flashBuffer[0]);                          
              break;                 
        case 'p':
              for(i=0;;){
                if(flashBuffer[i]==0x7F){
                  sciout(0x7F);
                  break;  
                }
                sciout(flashBuffer[i++]);      
              }  
        case 0xBC: //Program Plug 1                                          
              clearFLASHbuffer();
              for(i=0;i<128;i++){                    
                while(rxBuffer_size()==0);
                temp=sciread();                
                flashBuffer[i]=temp;
                if(flashBuffer[i]==0x7F){
                  break;  
                }                  
              }                                                
              FLASHProgram(PLUG1_ADDR);                
              sciout(0xFC);
              updatePlug = 1;
              break;
        case 0xBD: //Program Plug 2               
              clearFLASHbuffer();
              for(i=0;i<128;i++){
                while(rxBuffer_size()==0); 
                temp=sciread();                
                flashBuffer[i]=temp;
                if(flashBuffer[i]==0x7F){
                  break;  
                }                  
              }                                                 
              FLASHProgram(PLUG2_ADDR);                
              sciout(0xFC);
              updatePlug = 1;
              break;
        case 0xBE: //Program Plug 3               
              clearFLASHbuffer();
              for(i=0;i<128;i++){
                while(rxBuffer_size()==0); 
                temp=sciread();
                flashBuffer[i]=temp;
                if(flashBuffer[i]==0x7F){
                  break;  
                }                  
              }                                                 
              FLASHProgram(PLUG3_ADDR);                
              sciout(0xFC);
              updatePlug = 1;
              break;
        case 0xBF: //Program Plug 4               
              clearFLASHbuffer();
              for(i=0;i<128;i++){
                while(rxBuffer_size()==0); 
                temp=sciread();
                flashBuffer[i]=temp;
                if(flashBuffer[i]==0x7F){
                  break;  
                }                  
              }                                                 
              FLASHProgram(PLUG4_ADDR);                
              sciout(0xFC); 
              updatePlug = 1;
              break;
        case 0x3C:
              getPlug(1);
              break;
        case 0x3D:
              getPlug(2);
              break;
        case 0x3E:
              getPlug(3);
              break;
        case 0x3F:
              getPlug(4);
              break;
        case 0x7C:
              setTime();
              break;
        case 0x7D:
              sciout(currentHour);
              sciout(currentMinute);
              sciout(currentDay); 
              break;   
        case 0xFE:
              FLASHErase(PLUG1_ADDR);
              FLASHErase(PLUG2_ADDR);
              FLASHErase(PLUG3_ADDR);
              FLASHErase(PLUG4_ADDR); 
              break;
        default: //Else
                break;
      }      
    
    }                       
  
  } /* loop forever */

}

